-module(assembler).

-author('abdoo.mahran@gmail.com').

%%% Include file
-include("assembler.hrl").


-export([first_pass/1, second_pass/2]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%                                              %%%
%%%               External Functions             %%%
%%%                                              %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% implementation of First pass
first_pass(FileName) ->
    clean_up(),
    io:format("~nFirst pass Started... ~n"),
    io:format("======================= ~n"),
    erlang:put(lc,0), erlang:put(lc1,0),
    ets:new(codeFirstPass, [ordered_set, named_table]),
    ets:new(label, [ordered_set, named_table, {keypos, 2}]),
    Fd = assembler_file:open(FileName),
    first_pass_help(Fd, FileName).

%%% Second pass implementation
second_pass(Fd, FileName) ->
    %io:format("~nSecond pass Started... ~n"),
    %io:format("======================= ~n"),
    assembler_file:close(Fd),
    Fd1 = assembler_file:open(FileName),
    ets:new(codeSecondPass, [ordered_set, named_table]),
    Result = second_pass_help(Fd1),
    assembler_file:close(Fd1),
    clean_up(),
    Result.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%                                              %%%
%%%               Internal Functions             %%%
%%%                                              %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clean_up() ->
    ets:delete(codeFirstPass),
    ets:delete(codeSecondPass),
    ets:delete(label).

first_pass_help(Fd, FileName) ->
    case assembler_file:read_line(Fd) of
        eof ->
            exit("Error: No END found in the program");
        Line ->
            case parse_line(Line, ?FIRSTPASS_DEL) of
                emptyLine ->
                    %%% Increment LC
                    CurrentLC = erlang:get(lc),
                    erlang:put(lc, CurrentLC+1),
                    first_pass_help(Fd, FileName);
                {org, LC} ->
                    %%% Set LC
                    erlang:put(lc, list_to_integer(LC)),
                    ets:insert(codeFirstPass, {000, "ORG"++LC}),
                    first_pass_help(Fd, FileName);
                firstEnd ->
                    io:format("Code ~p~n",[ets:tab2list(codeFirstPass)]),
                    io:format("Lables ~p~n",[ets:tab2list(label)]),
                    %io:format("======================= ~n"),
                    %io:format("~nFirst pass Finished... ~n"),
                    second_pass(Fd, FileName);
                {continue, Tokens} ->
                    %%% Increment LC
                    CurrentLC = erlang:get(lc),
                    ets:insert(codeFirstPass,{CurrentLC,string:join(Tokens," ")}),
                    erlang:put(lc, CurrentLC+1),
                    first_pass_help(Fd, FileName);
                {Label, MLine} ->
                    %%% Store address in the table
                    %%% increment LC
                    CurrentLC = erlang:get(lc),
                    case string:to_integer(Label) of
                        {error, no_integer} ->
                            case length(Label) =< 3 of
                                 true ->
                                   ets:insert(codeFirstPass,{CurrentLC, MLine}),
                                    ets:insert(label,
                                           {Label,integer_to_list(CurrentLC)}),
                                    erlang:put(lc, CurrentLC+1),
                                    first_pass_help(Fd, FileName);
                                _ ->
                                    exit("Label " ++ Label ++ " should be 1-3 letters maximum at line " ++ integer_to_list(CurrentLC))
                            end;
                        _ ->
                            exit("Label " ++ Label ++ " should not start with integer at line " ++ integer_to_list(CurrentLC))
                    end
            end
    end.

%%% Parses Line.
parse_line(Line, Delimiter) ->
    M = string:tokens(Line, "\n"),
    MLine = if
                M == []  ->
                    [];
                M /= []->
                    [M1] = M,
                    M1
    end,
    Tokens = string:tokens(MLine, Delimiter),
    case length(Tokens) of
        1 ->
            [OrgEndCheck] = Tokens,
            check_org_end(string:tokens(OrgEndCheck, ?SECONDPASS_DEL));
        0 ->
            %% Empty line
            emptyLine;
        _ ->
            [Label|_] = Tokens,
            {Label, MLine}
    end.

%%% check_org_end for first_pass.
check_org_end(["ORG", LC|_]) ->
    {org, LC};
check_org_end(["END"|_]) ->
    firstEnd;
check_org_end(Tokens) ->
    {continue, Tokens}.

second_pass_help(Fd) ->
    case assembler_file:read_line(Fd) of
        eof ->
            %io:format("======================= ~n"),
            %io:format("Second pass Finished... ~n"),
            ok;
        Line ->
            case parse_line(Line) of
                {org, LC, _Command} ->
                    %%% Set LC
                    erlang:put(lc1, list_to_integer(LC)),
                    %ets:insert(codeSecondPass, {000, "ORG"++LC}),
                    second_pass_help(Fd);
                {secondEnd, _Command} ->
                    Result = ets:tab2list(codeSecondPass),
                    assembler_util:format(Result),
                    Result;
                emptyLine ->
                    %%% Increment LC
                    CurrentLC = erlang:get(lc1),
                    erlang:put(lc1, CurrentLC+1),
                    second_pass_help(Fd);
                {error, Command} ->
                    CurrentLC = erlang:get(lc1),
                    io:format("**Error in ~p at line ~p~n",[Command,CurrentLC]),
                    erlang:put(lc1, CurrentLC+1),
                    second_pass_help(Fd);
                {comment_error, Command} ->
                    CurrentLC = erlang:get(lc1),
                    io:format("**Error in writing comment in ~p at line ~p~n",[Command,CurrentLC]),
                    erlang:put(lc1, CurrentLC+1),
                    second_pass_help(Fd);
                {hex, Value, Command} ->
                    CurrentLC = erlang:get(lc1),
                    case assembler_util:hex2bin(Value) of
                        {error, _Hex} ->
                            io:format("** Error in ~p at line~p~n",
                                      [Command, CurrentLC]),
                            second_pass_help(Fd);
                        NValue ->
                            ets:insert(codeSecondPass,
                                       {integer_to_list(CurrentLC),NValue}),
                            erlang:put(lc1, CurrentLC+1),
                            second_pass_help(Fd)
                    end;
                {dec, Value, _Command} ->
                    %% Convert Value to Binary
                    CurrentLC = erlang:get(lc1),
                    ets:insert(codeSecondPass, {integer_to_list(CurrentLC),
                                                assembler_util:dec2bin(Value)}),
                    erlang:put(lc1, CurrentLC+1),
                    second_pass_help(Fd);
                {Address, Command} ->
                    CurrentLC = erlang:get(lc1),
                    case assembler_util:hex2bin(Address) of
                        {error, _Hex}->
                            io:format("** Error in ~p at line~p~n",
                                      [Command, CurrentLC]);
                        BAddress ->
                            ets:insert(codeSecondPass,
                                       {integer_to_list(CurrentLC), BAddress}),
                            erlang:put(lc1, CurrentLC+1),
                            second_pass_help(Fd)
                    end
            end
    end.

parse_line(Line) ->
    M = string:tokens(Line, "\n"),
    MLine = if
                M == []  ->
                    [];
                M /= []->
                    [M1] = M,
                    M1
            end,
    case check_comment(MLine) of
        {comment_error, Command} ->
            {comment_error, Command};
        RCommand ->
            check_commands(RCommand)
    end.

check_commands([]) ->
    emptyLine;
check_commands(["ORG", LC]=Command) ->
    {org, LC, Command};
check_commands(["END"] = Command) ->
    {secondEnd, Command};
%%% MRI with direct address
check_commands(["AND", L,"I"] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"8"++Address, Command};
check_commands(["ADD", L,"I"] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"9"++Address, Command};
check_commands(["LDA", L, "I"] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"A"++Address, Command};
check_commands(["STA", L, "I"] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"B"++Address, Command};
check_commands(["BUN", L, "I"] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"C"++Address, Command};
check_commands(["BSA", L, "I"] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"D"++Address, Command};
check_commands(["ISZ", L, "I"] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"E"++Address, Command};
%%% MRI with Indirect address
check_commands(["AND", L] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"0"++Address, Command};
check_commands(["ADD", L] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"1"++Address, Command};
check_commands(["LDA", L] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"2"++Address, Command};
check_commands(["STA", L] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"3"++Address, Command};
check_commands(["BUN", L] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"4"++Address, Command};
check_commands(["BSA", L] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"5"++Address, Command};
check_commands(["ISZ", L] = Command)->
    [[Address]] = ets:match(label, {L,'$1'}),
    {"6"++Address, Command};
%%% Non Memory address (NMRI)
check_commands(["CLA"] = Command)->
    {"7800", Command};
check_commands(["CLE"] = Command)->
    {"7400", Command};
check_commands(["CMA"] = Command)->
    {"7200", Command};
check_commands(["CME"] = Command)->
    {"7100", Command};
check_commands(["CIR"] = Command)->
    {"7080", Command};
check_commands(["CIL"] = Command)->
    {"7040", Command};
check_commands(["INC"] = Command)->
    {"7020", Command};
check_commands(["SPA"] = Command)->
    {"7010", Command};
check_commands(["SNA"] = Command)->
    {"7008", Command};
check_commands(["SZA"] = Command)->
    {"7004", Command};
check_commands(["SZE"] = Command)->
    {"7002", Command};
check_commands(["HLT"] = Command)->
    {"7001", Command};
%%% I/O Instructions
check_commands(["INP"] = Command)->
    {"F800", Command};
check_commands(["OUT"] = Command)->
    {"F400", Command};
check_commands(["SKI"] = Command)->
    {"F200", Command};
check_commands(["SKO"] = Command)->
    {"F100", Command};
check_commands(["ION"] = Command)->
    {"F080", Command};
check_commands(["IOF"] = Command)->
    {"F040", Command};
check_commands(["DEC", Value] = Command) ->
    {dec, Value, Command};
check_commands([_Label, "DEC", Value] = Command) ->
    {dec, Value, Command};
check_commands(["HEX", Value] = Command) ->
    {hex, Value, Command};
check_commands([_Label, "HEX", Value] = Command) ->
    {hex, Value, Command};
check_commands(Command)->
    {error, Command}.

check_comment(Command) ->
    [Command1|Comment] = string:tokens(Command, ?COMMENT_DEL),
    RCommand = string:tokens(Command1, ?SECONDPASS_DEL),
    N = length(RCommand),
    case Comment of
        [] when N =< 3 ->
            RCommand;
        Comment when N =<3 ->
            RCommand;
        _ ->
            {comment_error, Command}
    end.
